define(
   [
      'dojo/_base/declare',
      'dojo/_base/lang',
      'dojo/json',
      'dojo/Evented',
      'dojo/Deferred',
      'noBusinessBase/heartbeat/WebSocketConnection',
      'noBusinessBase/heartbeat/LongPollConnection',
      'dojo/i18n!pm/nls/mainLocalizedResources'
   ],
   function(declare, lang, JSON, Evented, Deferred, WebSocketConnection, LongPollConnection, mainLocalizedResources)
   {
      return declare([Evented],
      {
         VERSION_CHANNEL: 1,
         CHANNEL_LIST_CHANNEL: 2, 
         PING_PONG_CHANNEL: 4,
         AUTHENTICATION_CHANNEL: 5,
         host: '',
         port: 0,
         mChannelsReceived: false,
         mClientVersion: '1.0.0',
         mReady: false,
         mClosed: true,
         mServerChannels: null,
         mVersionChecked: false,
         mConnection: null,
         mPingPongTimeoutTime: 60000,
         mPingPongTimeout: null,
         mPingPongInterval: 5000,
         mPingPong: null,
         usingSsl: false,
         constructor:function(args)
         {
            dojo.safeMixin(this, args);
         },
         postscript: function(args)
         {
            this.inherited(arguments);
      
            if(typeof(window.WebSocket) !== "undefined")
               this.mConnection = new WebSocketConnection({host: this.host, port: this.port, usingSsl: this.usingSsl});
            else
               this.mConnection = new LongPollConnection({host: this.host, port: this.port});

            this.mClosed = false;
            this.mConnection.on("connectionMessage", lang.hitch(this, this.onMessage));
            this.mConnection.on("connectionClosed", lang.hitch(this, this.connectionClosed));
         },
         sendAuthentication: function(authData)
         {
            this.sendData("Library_Authentication", authData);
         },
         sendPing: function()
         {
            /*
            this.sendData("Library_Ping_Pong",
            {
               "p" : "p"
            });

            if(this.mPingPongTimeout)
               clearTimeout(this.mPingPongTimeout);

            this.mPingPongTimeout = setTimeout(lang.hitch(this, this.close), this.mPingPongTimeoutTime);
            */
         },
         sendData: function(channelName, pData)
         {
            if(!this.mReady)
               return;

            var pPacket = 
            {
               header: 
               {
                  channel:this.channelId(channelName)
               },
               message: pData
            };
            var jsonStr = JSON.stringify(pPacket);

            this.mConnection.write(jsonStr);
         },
         channelName: function(id)
         {
            for(var channelObjIdx in this.mServerChannels)
            {
               if(!this.mServerChannels.hasOwnProperty(channelObjIdx))
                  continue;

               for(var channelNameIdx in this.mServerChannels[channelObjIdx])
               {
                  if(id == this.mServerChannels[channelObjIdx][channelNameIdx])
                     return channelNameIdx;
               }
            }
            return null;
         },
         channelId: function(name)
         {
            for(var channelObjIdx in this.mServerChannels)
            {
               if(!this.mServerChannels.hasOwnProperty(channelObjIdx))
                  continue;

               for(var channelNameIdx in this.mServerChannels[channelObjIdx])
               {
                  if(name == channelNameIdx)
                     return this.mServerChannels[channelObjIdx][channelNameIdx];
               }
            }
            return null;
         },
         close: function()
         {
            if(this.mClosed)
               return;
            if(this.mPingPongTimeout)
               clearTimeout(this.mPingPongTimeout);

            if(this.mPingPong)
               clearInterval(this.mPingPong);

            this.mConnection.close();
            this.mClosed = true;
         },
         connectionClosed: function()
         {
            document.getElementsByTagName('HTML')[0].innerHTML=mainLocalizedResources.heartbeatErrorMessage; 
            alert(mainLocalizedResources.heartbeatErrorMessage); 
         },
         onMessage: function(data)
         {
            var channelId = data.header.channel;
            if(channelId == this.VERSION_CHANNEL)
            {
               var serverVersion = data.message.version;
               if(!this.versionCheck(serverVersion))
               {
                  this.close();
                  return;
               }

               this.mVersionChecked = true;
               this.readyCheck();
               return;
            }

            if(channelId == this.CHANNEL_LIST_CHANNEL)
            {
               this.mServerChannels = data.message.channels;
               this.mChannelsReceived = true;
               this.emit(this.mServerChannels);
               this.readyCheck();
               return;
            }

            if(channelId == this.PING_PONG_CHANNEL)
            {
               if(this.mPingPongTimeout)
               {
                  clearTimeout(this.mPingPongTimeout); 
                  this.mPingPongTimeout = null;
               }

               return;
            }

            if(channelId == this.AUTHENTICATION_CHANNEL)
            {
               this.emit("authenticated");
            }

            this.emit(this.channelName(channelId), data.message);
         },
         readyCheck: function()
         {
            if(!this.mChannelsReceived || !this.mVersionChecked)
               return;

            this.mReady = true;
            this.mPingPong = setInterval(lang.hitch(this, this.sendPing), this.mPingPongInterval);
            this.emit('ready');
         },
         getChannelNumbersFromNames: function(channelNames)
         {
            var pChannelNumbers =  new Array();
            for(var i = 0; i < channelNames.length; ++i)
            {
               var channelNumber = this.channelId(channelNames[i]);
               if(!channelNumber)
                  continue;

               pChannelNumbers.push(channelNumber);
            }

            return pChannelNumbers;
         },
         subscribeTo: function(channelNames)
         {
            if(!this.mChannelsReceived)
               return;

            var pChannelNumbers = this.getChannelNumbersFromNames(channelNames);
            if(!pChannelNumbers.length)
               return;

            this.sendData("Library_Channel_Subscription", 
            {
               "channels" : pChannelNumbers
            });
         },
         unsubscribeFrom: function(channelNames)
         {
            if(!this.mChannelsReceived)
               return;

            var pChannelNumbers = this.getChannelNumbersFromNames(channelNames);
            if(!pChannelNumbers.length)
               return;

            this.sendData("Library_Channel_Subscription",
            {
               "channels" : pChannelNumbers,
               "unsubscribe" : true
            });
         },
         versionCheck: function(serverVersion)
         {
            var serverxyz = serverVersion.split('.');
            var clientxyz = this.mClientVersion.split('.');
            if(serverxyz.length != 3 || clientxyz.length != 3)
               return false;

            if(clientxyz[0] != serverxyz[0] || clientxyz[1] != serverxyz[1])
               return false;

            return true;
         },
         whenReady: function()
         {
            var pDeferred = new Deferred();
            if(!this.mReady)
            {
               this.on('ready', lang.hitch(this, function()
               {
                  this.sendResolved(pDeferred);
               }));
            }
            else
            {
               this.sendResolved(pDeferred);
            }
            return pDeferred;
         },
         sendResolved: function(pDeferred)
         {
            setTimeout(lang.hitch(this, function(){
               pDeferred.resolve(this);
            }), 1);
         }
      });
   }
);
